/**
 * DLA/Oscilloscope Project Software
 * 
 * By Kevin Cuzner <kevincuzner@yahoo.com>
 * 
 * This file is part of the DLA/Oscilloscope Project Software.
 *
 * The DLA/Oscilloscope Project Software is free software: you can 
 * redistribute it and/or modify it under the terms of the GNU Lesser
 * General Public License as published by the Free Software Foundation,
 * either version 3 of the License, or (at your option) any later version.
 *
 * The DLA/Oscilloscope Project Software is distributed in the hope
 * that it will be useful, but WITHOUT ANY WARRANTY; without even the
 * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with The DLA/Oscilloscope Project Software.
 * If not, see <http://www.gnu.org/licenses/>.
 */


using System;
using System.Collections.Generic;

namespace DLAOscilloscopegtk
{
	/// <summary>
	/// Base class for Analog Channels based on functions dependant on other channels
	/// </summary>
	/// <exception cref='NotImplementedException'>
	/// Is thrown when a requested operation is not implemented for a given type.
	/// </exception>
	/// <exception cref='ArgumentOutOfRangeException'>
	/// Is thrown when an argument passed to a method is invalid because it is outside the allowable range of values as
	/// specified by the method.
	/// </exception>
	public class FunctionAnalogChannel : IAnalogChannel
	{
		protected Dictionary<IAnalogChannel, string> dependants;
		protected string equation;
		protected double zero;
		protected double scaling;
		protected ulong timebase;
		
		/// <summary>
		/// Initializes a new instance of the <see cref="DLAOscilloscopegtk.FunctionAnalogChannel"/> class based
		/// on an equation.
		/// </summary>
		/// <param name='equation'>
		/// The equation to use.
		/// </param>
		/// <param name='timebase'>
		/// Timebase in Hz to use for this analog channel
		/// </param>
		public FunctionAnalogChannel (string equation, ulong timebase)
		{
			this.zero = 0;
			this.scaling = 1;
			this.equation = equation;
		}
		
		/// <summary>
		/// Adds a channel that this depends on along with the string value of its variable
		/// in the set equation. Adding a channel that has already been added will update its
		/// variable string. Variables must be a single character a-z or A-Z.
		/// </summary>
		/// <returns>
		/// The dependant.
		/// </returns>
		/// <param name='variable'>
		/// The string variable name to associate this with. Must be a single character a-zA-Z.
		/// </param>
		/// <param name='channel'>
		/// The channel to add as a dependant.
		/// </param>
		public bool AddDependant (string variable, IAnalogChannel channel)
		{
			//first see if we already have it
			if (this.dependants.ContainsKey (channel)) {
				//update this channels variable
				this.dependants [channel] = variable;
			} else {
				//add this channel to the dependants
				this.dependants.Add (channel, variable);
			}
			
			//check to see if this variable is inside the equation
			if (GetVariableList ().Contains (variable)) {
				//the variable string was found
				return true;
			}
			
			//the variable string was not found
			return false;
		}
		
		/// <summary>
		/// Removes a channel from this FunctionAnalogChannel
		/// </summary>
		/// <returns>
		/// Whether or not it was removed
		/// </returns>
		/// <param name='channel'>
		/// The channel to remove
		/// </param>
		public bool RemoveDependant (IAnalogChannel channel)
		{
			//remove the channel
			return this.dependants.Remove (channel);
		}
		
		/// <summary>
		/// Gets the value of a point at x based on this channel's equation.
		/// </summary>
		/// <returns>
		/// The <see cref="System.Double"/> value.
		/// </returns>
		/// <param name='x'>
		/// The point to evaluate.
		/// </param>
		public double GetPointAt (int x)
		{
			Dictionary<string, double > values = new Dictionary<string, double> ();
			ulong origTimeBase;
			//grab all the values from the dependents into the values dictionary and set their timebases to ours
			foreach (KeyValuePair<IAnalogChannel, string> pair in this.dependants) {
				origTimeBase = pair.Key.TimeBase; //save their timebase
				pair.Key.TimeBase = this.timebase; //change it to our timebase
				values.Add (pair.Value, pair.Key.GetPointAt (x));
				pair.Key.TimeBase = origTimeBase; //reset their timebase back to whatever it was before
			}
			
			return EquationParser.EvaluateEquation (this.equation, values);
		}
		
		public double GetPointAt (int x, params double[] arguments)
		{
			throw new NotImplementedException ();
		}
		
		/// <summary>
		/// Gets a list of variables in the equation.
		/// </summary>
		/// <returns>
		/// The variable list.
		/// </returns>
		private List<string> GetVariableList ()
		{
			return EquationParser.ExtractVariablesFromEquation (this.equation);
		}
		
		/// <summary>
		/// Gets a value indicating whether this <see cref="DLAOscilloscopegtk.FunctionAnalogChannel"/>'s equation is valid.
		/// </summary>
		/// <value>
		/// <c>true</c> if equation valid; otherwise, <c>false</c>.
		/// </value>
		public bool EquationValid {
			get {
				return EquationParser.ValidateEquation (this.equation);
			}
		}
		
		/// <summary>
		/// Gets or sets the equation for this channel.
		/// </summary>
		/// <value>
		/// The equation for this channel.
		/// </value>
		public string Equation {
			get {
				return equation;
			}
			set {
				equation = value;
			}
		}
		
		/// <summary>
		/// Gets the native time base. <see cref="DLAOscilloscopegtk.FunctionAnalogChannel" /> has no
		/// NativeTimeBase so this property has no effect.
		/// </summary>
		/// <value>
		/// The native time base.
		/// </value>
		public ulong NativeTimeBase {
			get {
				return 0; //there is no native time base with analog function channels
			}
		}
		
		public double Scaling {
			get {
				return scaling;
			}
			set {
				if (scaling > 0) {
					//the scaling must be greater than 0
					throw new ArgumentOutOfRangeException ();
				}
				scaling = value;
			}
		}
		
		/// <summary>
		/// Gets or sets the time base.
		/// </summary>
		/// <value>
		/// The time base.
		/// </value>
		public ulong TimeBase {
			get {
				return this.timebase;
			}
			set {
				this.timebase = value;
			}
		}
		
		public double Zero {
			get {
				return zero;
			}
			set {
				this.zero = value;
			}
		}
	}
}

